package org.aspire.math.geom;

import org.aspire.math.geom.event.PointEvent;
import org.aspire.math.geom.event.PointListener;
import org.aspire.util.Cache;

/******************************************************************************
 * The <code>Point</code> class encapsulates the x and y coordinate of an
 * object on the Cartesian plane stored in floating point precision.
 * 
 * @author JPACarabuena
 *****************************************************************************/
public class Point {

	// ========================================================================
	// Constants
	// ========================================================================

	// ========================================================================
	// Static Methods
	// ========================================================================
		
	// ========================================================================
	// Fields
	// ========================================================================
	
	/**************************************************************************
	 * The x-coordinate of this point data.
	 *************************************************************************/
	protected double x;
	
	/**************************************************************************
	 * The y-coordinate of this point data.
	 *************************************************************************/
	protected double y;
	
	/**************************************************************************
	 * A cache of listeners for this dimension.
	 *************************************************************************/
	private Cache listeners;
	
	// ========================================================================
	// Constructors
	// ========================================================================
	
	/**************************************************************************
	 * Creates a new point data with the specified x and y coordinates. 
	 * 
	 * @param x	the x-coordinate of this point.
	 * @param y	the y-coordinate of this point.
	 *************************************************************************/
	public Point(double x, double y) {
		this.x = x;
		this.y = y;
		this.listeners = new Cache();
	}
	
	/**************************************************************************
	 * Creates a new point data initialized at the origin.
	 *************************************************************************/
	public Point() {
		this(0.0f, 0.0f);
	}
	
	// ========================================================================
	// Getters & Setters
	// ========================================================================
	
	/**************************************************************************
	 * Gets the x-coordinate of this point data in floating point precision.
	 * 
	 * @return	where the data is in x-axis.
	 *************************************************************************/
	public double getX() {
		return this.x;
	}
	
	/**************************************************************************
	 * Gets the y-coordinate of this point data in floating point precision.
	 * 
	 * @return	where the data is in y-axis.
	 *************************************************************************/
	public double getY() {
		return this.y;
	}
	
	/**************************************************************************
	 * Gets the x-coordinate of this point data in integer precision.
	 * 
	 * @return	where the data is in x-axis.
	 *************************************************************************/
	public int getXInt() {
		return (int) this.x;
	}
	
	/**************************************************************************
	 * Gets the y-coordinate of this point data in integer precision.
	 * 
	 * @return	where the data is in y-axis.
	 *************************************************************************/
	public int getYInt() {
		return (int) this.y;
	}
	
	/**************************************************************************
	 * Sets the x-coordinate of this point data to the specified x-coordinate.
	 * 
	 * @param x	the new x-coordinate of this point data.
	 *************************************************************************/
	public void setX(double x) {
		this.triggerListeners(PointEvent.SRC_X, (int) (x-this.x), 0);
		this.x = x;
	}
	
	/**************************************************************************
	 * Sets the y-coordinate of this point data to the specified y-coordinate.
	 * 
	 * @param y	the new y-coordinate of this point data.
	 *************************************************************************/
	public void setY(double y) {
		this.triggerListeners(PointEvent.SRC_X, 0, (int) (y-this.y));
		this.y = y;
	}
	
	/**************************************************************************
	 * Sets the location of this point data to the specified location.
	 * 
	 * @param p	the new location for this point data.
	 *************************************************************************/
	public void setLocation(Point p) {
		this.setLocation(p.x, p.y);
	}
	
	/**************************************************************************
	 * Sets the location of this point data to the specified x and y 
	 * coordinates.
	 * 
	 * @param x	the new x-coordinate for this point data.
	 * @param y	the new y-coordinate for this point data.
	 *************************************************************************/
	public void setLocation(double x, double y) {
		this.triggerListeners(PointEvent.SRC_X, (int) (x-this.x), (int) (y-this.y));
		this.x = x;
		this.y = y;
	}
	
	// ========================================================================
	// Override Methods
	// ========================================================================
	
	@Override
	public Object clone() {
		return new Point(this.x, this.y);
	}
	
	@Override
	public boolean equals(Object object) {
		if(object instanceof Point) {
			return ((this.x == ((Point) object).x)
					&& (this.y == ((Point) object).y));
		}
		return false;
	}
	
	@Override
	public String toString() {
		return this.getClass() + "[x=" + this.x + ",y=" + this.y + "]";
	}
	
	// ========================================================================
	// Methods
	// ========================================================================

	/**************************************************************************
	 * Add the specified listener to this point.
	 * 
	 * @param listener the listener for this point.
	 *************************************************************************/
	public void addListener(PointListener listener) {
		this.listeners.insert(listener);
	}
	
	/**************************************************************************
	 * Remove the specified listener to this point.
	 * 
	 * @param listener	the listener for this point to be removed.
	 *************************************************************************/
	public void removeListener(PointListener listener) {
		this.listeners.remove(this.listeners.indexOf(listener));
	}

	/**************************************************************************
	 * Triggers event for the point listeners.
	 * 
	 * @param src	the source of change.
	 * @param deltaX	the change in width.
	 * @param deltaY	the change in height.
	 *************************************************************************/
	private void triggerListeners(int src, int deltaX, int deltaY) {
		int n = this.listeners.size();
		
		if(deltaX == 0 && deltaY == 0) return;
		
		if(deltaX >= 0 && deltaY >= 0) {
			for(int i=0; i<n; i++) {
				((PointListener) this.listeners.elementAt(i))
					.locationIncreased(new PointEvent(src, 
							deltaX, deltaY));
			}
		} else if(deltaX <= 0 && deltaY <= 0) {
			for(int i=0; i<n; i++) {
				((PointListener) this.listeners.elementAt(i))
					.locationDecreased(new PointEvent(src, 
							deltaX, deltaY));
			}
		} else {
			for(int i=0; i<n; i++) {
				((PointListener) this.listeners.elementAt(i))
					.locationChanged(new PointEvent(src, 
							deltaX, deltaY));
			}
		}
	}

	// ========================================================================
	// Inner & Anonymous Classes
	// ========================================================================
}